﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using Microsoft.Win32;

namespace Heaven.Diagnostics
{
    /// <summary>
    /// Высокоуровневый элемент управления для проведения тестирования
    /// </summary>
    public partial class DiagnosticControl : UserControl
    {
        #region Атрибуты


        #endregion

        #region Инициализация

        /// <summary>
        /// Конструктор по умолчанию
        /// </summary>
        public DiagnosticControl()
        {
            InitializeComponent();
            
            // Реакция на загрузку сборок с целью найти зависимые 
            // сборки, которые также соответствуют спецификации
            AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(OnAssemblyLoad);
        }

        #endregion

        #region Свойства

        /// <summary>
        /// Цвет разделительной линии
        /// </summary>
        public Color SplinterColor
        {
            get
            {
                return splitContainer.BackColor;
            }
            set
            {
                splitContainer.BackColor = value;
            }
        }

        /// <summary>
        /// Разрешить управление списком сборок
        /// </summary>
        public bool EnableAssemblyListManagment
        {
            get 
            {
                return toolStripButtonAddAssembly.Visible;
            }
            set
            {
                toolStripButtonAddAssembly.Visible = value;
                toolStripButtonSubAssembly.Visible = value;
                toolStripSeparatorAfterAssemblyList.Visible = value;
            }
        }

        #endregion

        #region Методы

        /// <summary>
        /// Существует ли сборка c таким именем в списке
        /// </summary>
        /// <param name="name">Имя сборки</param>
        /// <returns>True если существует, иначе false</returns>
        public bool IsExistAssembly(string name)
        {
            foreach (TreeNode node in testsTree.Nodes)
            {
                if (node.Text == name) return true;
            }
            return false;
        }

        /// <summary>
        /// Добавление сборки к проекту
        /// </summary>
        /// <param name="assembly"></param>
        public void AddAssembly(DiagnosedAssembly assembly)
        {
            // Во избежании мерцания и прочей дряни
            testsTree.BeginUpdate();

            // Создание нода для сборки в корне дерева
            TreeNode assemblyNode = testsTree.Nodes.Add(assembly.Name);
            assemblyNode.ToolTipText = assembly.Location;
            assemblyNode.Tag = assembly;

            // Первичную сборку выделяем
            if (testsTree.Nodes.Count == 1)
            {
                assemblyNode.Expand();
                assemblyNode.NodeFont = new Font(Font.SystemFontName, Font.SizeInPoints, FontStyle.Bold);
            }

            TreeNode groupNode = null;
            TreeNode testNode = null;

            foreach (Test test in assembly.Tests)
            {
                // Поиск группы с данным именем..
                TreeNode[] dummy = assemblyNode.Nodes.Find(test.Group, false);
                // Если не найдена - создаем, иначе запоминаем найденую
                if (dummy.Length == 0) groupNode = assemblyNode.Nodes.Add(test.Group, test.Group);
                else groupNode = dummy[0];

                // Добавление в эту группу теста
                testNode = groupNode.Nodes.Add(test.Name);
                // Ассоциируем с нодом тест
                testNode.Tag = test;
            }

            testsTree.EndUpdate();
        }

        void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            if (DiagnosedAssembly.IsCompatibleAssembly(args.LoadedAssembly))
            {
                AddAssembly(DiagnosedAssembly.Load(args.LoadedAssembly));
            }
        }

        #endregion

        #region Обработка событий

        // Обработка события нажатия на кнопку добавления сборки к проекту
        private void OnAddToolStripButtonClick(object sender, EventArgs e)
        {
            if (openAssemblyDialog.ShowDialog() != DialogResult.OK) return;

            Assembly asm = Assembly.LoadFile(openAssemblyDialog.FileName);
            if (!DiagnosedAssembly.IsCompatibleAssembly(asm))
            {
                MessageBox.Show(
                    "Assembly \"" + asm.GetName() + "\"\n" +
                    "can't be loaded. Most likely, it doesn't comply with a specification.     ",
                    "Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Exclamation);
                return;
            }
                        
            AddAssembly(DiagnosedAssembly.Load(asm));
        }

        // Обработка события нажатия на кнопку запуска выделенных тестов
        private void OnExecuteSelectedToolStripButtonClick(object sender, EventArgs e)
        {
            TreeNode[] nodes = { testsTree.SelectedNode };
            ExecuteTests(nodes);
            UpdateTestResultView();
        }

        // Обработка события выбора элемента дерева тестов
        private void OnAfterSelectTreeView(object sender, TreeViewEventArgs e)
        {
            UpdateToolBarState();
            UpdateTestResultView();
        }

        // Обработка события изменения размера 
        // контрола для просмотра результатов теста
        private void OnTestResultViewResize(object sender, EventArgs e)
        {
            int width = testResultView.Width - 22;

            testResultView.Columns[0].Width = (int)(width * 0.45);
            testResultView.Columns[1].Width = (int)(width * 0.20);
            testResultView.Columns[2].Width = (int)(width * 0.20);
            testResultView.Columns[3].Width = (int)(width * 0.15);
        }

        // Обработка события нажатия на кнопку удаления
        private void OnSubToolStripButtonClick(object sender, EventArgs e)
        {
            if (testsTree.Nodes.Contains(testsTree.SelectedNode))
            {
                testsTree.Nodes.Remove(testsTree.SelectedNode);
                UpdateToolBarState();
                UpdateTestResultView();
            }
        }

        // Обработка события нажатия на кнопку запуска всех тестов
        private void OnStartAllClick(object sender, EventArgs e)
        {            
            ExecuteAllTests();
            UpdateTestResultView();
        }

        private void OnSaveResultClick(object sender, EventArgs e)
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "XML|*.xml|Все файлы|*.*";

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                
                try
                {
                    WaitingForm.Show();
                    DiagnosedAssembly assembly = (DiagnosedAssembly)testsTree.SelectedNode.Tag;
                    assembly.SaveReport(dialog.FileName);
                    WaitingForm.Hide();
                }
                catch
                {
                    WaitingForm.Hide();
                    throw;
                }
            }
        }

        private void OnPrintFromFileClick(object sender, EventArgs e)
        {
            MessageBox.Show("Печать из файла не реализована (!)");
        }

        private void OnPrintCurrentClick(object sender, EventArgs e)
        {
            MessageBox.Show("Печать текущих результатов не реализована (!)");
        }

        private void OnAnalysisClick(object sender, EventArgs e)
        {
            AnalysisForm form = new AnalysisForm();
            form.Show(ParentForm);
        }

        #endregion

        #region Внутренние

        // Массив TreeNode[], производится запуск сопоставленного с каждым
        // теста (также просматриваются вложенные ноды)
        void ExecuteTests(TreeNode[] nodes)
        {
            foreach (TreeNode node in nodes)
            {
                ExecuteTestsRecursive(node);
            }
            UpdateAssemblyList();
        }

        // Массив TreeNode[], производится запуск сопоставленного с каждым
        // теста (также просматриваются вложенные ноды)
        void ExecuteAllTests()
        {
            foreach (TreeNode node in testsTree.Nodes)
            {
                ExecuteTestsRecursive(node);
            }
            UpdateAssemblyList();
        }

        // Запуск теста рекурсивно по дереву, т.е. 
        // начиная с указанного узла node на дереве 
        // тестов и дальше спускаясь по нему
        TestState ExecuteTestsRecursive(TreeNode node)
        {
            TestState teststate = TestState.Successful;
            
            if (node.Tag is Test)
            {
                Test test = (Test)node.Tag;
                test.Diagnose();
                UpdateTestState(node, test.Result);
                teststate = test.Result.Result;
            }
            else
            {
                foreach (TreeNode n in node.Nodes)
                {
                    TestState tmp = ExecuteTestsRecursive(n);
                    if (tmp == TestState.Failed)
                        teststate = TestState.Failed;
                    if ((tmp == TestState.Untested) && (teststate != TestState.Failed))
                        teststate = TestState.Untested;
                }
            }

            // Подсветка элементов
            node.ImageIndex = (int)teststate;
            node.SelectedImageIndex = node.ImageIndex;

            return teststate;
        }

        // Обновление состояния теста для определенного узла
        void UpdateTestState(TreeNode node, Collector acc)
        {
            node.ImageIndex = (int)acc.Result;
            node.SelectedImageIndex = (int)acc.Result;
        }
        
        // Обновление панели инструментов
        void UpdateToolBarState()
        {
            bool IsSelect = (testsTree.SelectedNode != null);
            bool IsTopNode = IsSelect ? (testsTree.SelectedNode.Level == 0) : false;

            toolStripButtonSubAssembly.Enabled = IsSelect && IsTopNode;
            toolStripButtonStartSelected.Enabled = IsSelect;
            toolStripButtonStartAll.Enabled = (testsTree.Nodes.Count > 0);
            toolStripButtonSaveResults.Enabled = IsSelect && IsTopNode;
        }

        // Обновление списка тестов
        void UpdateTestResultView()
        {
            // Предварительно чистим
            testResultView.Items.Clear();

            // Если узел выделен и содержит нужные данные
            if ((testsTree.SelectedNode != null) && (testsTree.SelectedNode.Tag is Test))
            {
                Test test = (Test)testsTree.SelectedNode.Tag;

                foreach (SubtestResult subtest in test.Result.Subtests)
                {
                    ListViewItem item = testResultView.Items.Add(subtest.Name);
                    item.SubItems.Add(subtest.Expected);
                    item.SubItems.Add(subtest.Actual);
                    item.SubItems.Add(subtest.ElapsedTime.ToString());
                    item.ImageIndex = (subtest.Result == TestState.Successful) ? 1 : 2;
                }

                // Если поизошло исключение, выводим информацию о нем
                Exception exception = test.Result.Exception;
                if (exception != null)
                {
                    ListViewItem item = testResultView.Items.Add("The tests were interrupted by the exception (!)");
                    item.BackColor = Color.Red;
                    item.ForeColor = Color.White;
                    item.ToolTipText = DecryptException(exception);
                    item.ImageIndex = 3;                    
                }
            }
        }

        string DecryptException(Exception exception)
        {
            string text =
                    "Exception type: " + exception.GetType().Name + "\n" +
                    "Message: " + exception.Message;
            string stackinfo = "";

            try
            {
                stackinfo = "\n\nStack trace:";

                StackTrace trace = new StackTrace(exception);
                foreach (StackFrame frame in trace.GetFrames())
                {
                    stackinfo += String.Format("\nClass: {0}, method: {1}, line {2}",
                        frame.GetMethod().DeclaringType.Name,
                        frame.GetMethod().Name,
                        frame.GetFileLineNumber());
                }
            }
            catch { stackinfo = ""; }

            return text + stackinfo;
       }

        void UpdateAssemblyList()
        {
            /*// Ищем другие сборки, которые загружены 
            // в домен и поддерживают спецификацию
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (DiagnosedAssembly.IsCompatibleAssembly(asm))
                {
                    DiagnosedAssembly diagnosed = DiagnosedAssembly.Load(asm);
                    if (!IsExistAssembly(diagnosed.Name))
                    {
                        AddAssembly(diagnosed);
                    }
                }
            }*/
        }

        #endregion
    }
}
